package org.shj.spring.boot.lang;

import com.alibaba.fastjson.util.TypeUtils;
import com.esotericsoftware.reflectasm.MethodAccess;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import net.sf.cglib.beans.BeanCopier;
import org.apache.commons.beanutils.ConversionException;
import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.beanutils.converters.DateConverter;
import org.apache.commons.beanutils.converters.IntegerConverter;
import org.apache.commons.beanutils.converters.StringConverter;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.shj.spring.boot.lang.exception.BeanMapperException;
import org.shj.spring.boot.lang.factory.MapperEntityFactory;
import org.shj.spring.boot.lang.util.ClassUtil;
import org.shj.spring.boot.util.DateUtil;
import org.shj.spring.boot.util.EnumUtil;
import org.shj.spring.boot.lang.util.ReflectionUtil;
import org.shj.spring.boot.util.StringUtil;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.sql.Timestamp;
import java.util.*;
import java.util.function.Function;

import static org.shj.spring.boot.lang.util.ClassUtil.*;

/**
 * Created by xuwei on 2017/3/29.
 */
public class BeanMapper {

    private final static Map<String, BeanCopier> BEAN_COPIER_MAP = Maps.newHashMap();

    static {
        init();
    }

    private static void init() {
        DateConverter dateConverter = new DateConverter();
        dateConverter.setPatterns(new String[]{"MM/dd HH:mm", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm:ss.SSS", "yyyy-MM-dd HH:mm:ss.SS"});
        register(dateConverter, Date.class);
        register(dateConverter, Timestamp.class);
        IntegerConverter integerConverter = new IntegerConverter(null);
        register(integerConverter, Integer.TYPE);
        register(integerConverter, Integer.class);
        register(new LongConverter(), Long.class);
        register(new StringConverter(), Comparable.class);
    }

    public static void register(org.apache.commons.beanutils.Converter converter, Class<?> clazz) {
        ConvertUtils.register(converter, clazz);
    }

    /**
     * 将source对象浅拷贝成targetClass类型的对象.
     *
     * @param source      源
     * @param targetClass 目标类型
     * @param <T>         目标class的泛型
     * @return 目标对象.
     */
    public static <T> T map(Object source, Class<T> targetClass) {
        if (source == null || targetClass == null) {
            return null;
        }
        if (isPrimaryOrWrapClass(targetClass)) {
            return TypeUtils.castToJavaBean(source, targetClass);
        } else if (targetClass.isEnum() || source.getClass().isEnum()) {
            return deepMapEnum(source, targetClass);
        } else {
            T target = newInstance(targetClass);
            copy(source, target);
            return target;
        }
    }

    private static <T> T deepMapEnum(Object source, Class<T> targetClass) {
        if (targetClass.isEnum()) {
            Integer ordinal = null;
            if (Integer.class.isAssignableFrom(source.getClass())) {
                ordinal = (Integer) source;
            } else if (NumberUtils.isDigits(StringUtil.toString(source))) {
                ordinal = Integer.valueOf(StringUtil.toString(source));
            }
            if (ordinal != null) {
                return EnumUtil.parse(ordinal, targetClass);
            }
        }
        if (source.getClass().isEnum()) {
            return deepMap(((Enum) source).ordinal(), targetClass);
        }
        return null;
    }

    /**
     * 浅拷贝源对象到目标对象.
     *
     * @param source       源对象实体
     * @param targetObject 目标对象实体
     */
    public static void copy(Object source, Object targetObject) {
        if (source == null || targetObject == null) {
            return;
        }
        Class srcClass = source.getClass();
        Class targetClass = targetObject.getClass();
        BeanCopier copier = getBeanCopier(srcClass, targetClass, false);
        copier.copy(source, targetObject, null);
    }

    public static <T> T newInstance(Class<T> targetClass) {
        if (ClassUtil.isInterfaceOrAbstractClass(targetClass)) {
            targetClass = MapperEntityFactory.get().getInstanceType(targetClass);
        }
        T target;
        try {
            target = targetClass.newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            throw new BeanMapperException(String.format("Class[%s] cannot initialized .", targetClass.getSimpleName()), e);
        }
        return target;
    }

    private static String generateKey(Class<?> class1, Class<?> class2) {
        return Joiner.on("_").join(class1.toString(), class2.toString());
    }

    /**
     * 将source对象沉拷贝成targetClass类型的对象.
     * 不可在convertTo中使用deepMap(this, targetClass), 会引起循环引用问题.
     *
     * @param source      源
     * @param targetClass 目标类型
     * @param <T>         目标class的泛型
     * @return 目标所在的对象中的字段对象
     */
    public static <T> T deepMap(Object source, Class<T> targetClass) throws ConversionException {
        if (source == null || targetClass == null) {
            return null;
        }
        if (targetClass.isAssignableFrom(source.getClass())) {
            return (T) source;
        } else if (targetClass.isEnum() || source.getClass().isEnum()) {
            return deepMapEnum(source, targetClass);
        } else if (isPrimaryOrWrapClass(targetClass) || Date.class.isAssignableFrom(targetClass)) {
            try {
                return TypeUtils.castToJavaBean(source, targetClass);
            } catch (Exception e) {
                try {
                    return (T) ConvertUtils.convert(source, targetClass);
                } catch (Exception e1) {
                    throw new ConversionException(String.format("cannot convert source[%s] to target[%s]", source, targetClass.getSimpleName()));
                }
            }
        } else if (String.class.isAssignableFrom(targetClass)) {
            try {
                return (T) ConvertUtils.convert(source, targetClass);
            } catch (Exception e) {
                return (T) StringUtil.toString(source);
            }
        } else if (Map.class.isAssignableFrom(targetClass)) {
            Map map = createMap((Class<? extends Map>) targetClass);
            map.putAll(ReflectionUtil.unpackageFields(source));
            return (T) map;
        } else {
            T t = newInstance(targetClass);
            deepCopy(source, t);
            return t;
        }
    }

    /**
     * 将source对象沉拷贝成满足targetField字段类型的对象.
     * 不可在convertTo中使用deepMap(this, targetClass), 会引起循环引用问题.
     *
     * @param src         源
     * @param targetField 目标所在的对象中的字段类型
     * @return 目标对象.
     */
    public static Object deepMap(Object src, Field targetField) {
        return deepMap(src, targetField, null);
    }

    /**
     * 将source对象沉拷贝成满足targetField字段类型的对象.
     * 不可在convertTo中使用deepMap(this, targetClass), 会引起循环引用问题.
     *
     * @param sourceList  源Collection
     * @param targetField 目标Collection的字段类型
     * @return 目标对象.
     */
    public static Collection deepMap(Collection sourceList, Field targetField) {
        if (sourceList == null) {
            return null;
        }
        Class targetClass = targetField.getType();
        if (!Collection.class.isAssignableFrom(targetClass)) {
            return null;
        }
        Collection valueList = createCollection((Class<? extends Collection>) targetClass, sourceList.size());
        if (valueList == null) {
            return null;
        }
        CollComponentType collAnno = targetField.getAnnotation(CollComponentType.class);
        if (collAnno != null) {
            for (Object data : sourceList) {
                Object value = deepMap(data, collAnno.value());
                if (value != null) {
                    valueList.add(value);
                }
            }
        }
        return valueList;
    }

    /**
     * 将source对象沉拷贝成满足targetClass类型的对象.
     * 不可在convertTo中使用deepMap(this, targetClass), 会引起循环引用问题.
     *
     * @param sourceList  源Collection
     * @param targetClass 目标Collection中的值类型
     * @return 目标对象.
     */
    public static <S extends Collection<T>, T> S deepMap(Collection sourceList, Class<T> targetClass) {
        return (S) deepMap(sourceList, sourceList.getClass(), targetClass);
    }


    public static <T> T deepMapArray(Collection sourceList, Class<T> targetClass) {
        if (!targetClass.isArray()) {
            return null;
        }
        Object array = Array.newInstance(targetClass.getComponentType(), CollectionUtils.size(sourceList));
        if (CollectionUtils.isNotEmpty(sourceList)) {
            int i = 0;
            for (Object source : sourceList) {
                T target = deepMap(source, targetClass);
                Array.set(array, i++, target);
            }
        }
        return (T) array;
    }

    public static <S extends Collection<T>, T> S deepMap(Collection sourceList, Class<S> listClass, Class<T> targetClass) {
        if (sourceList == null) {
            return null;
        }
        Collection targetList = createCollection(listClass, sourceList.size());
        if (targetList == null) {
            return null;
        }
        for (Object source : sourceList) {
            T target = deepMap(source, targetClass);
            targetList.add(target);
        }
        return (S) targetList;
    }

    /**
     * 将source对象沉拷贝成满足targetField字段类型的对象.
     * 不可在convertTo中使用deepMap(this, targetClass), 会引起循环引用问题.
     *
     * @param src         源Map
     * @param targetField 目标所在的对象中的字段类型
     * @return 目标对象.
     */
    public static Object deepMap(Map src, Field targetField) {
        Class targetClass = targetField.getType();
        if (Map.class.isAssignableFrom(targetClass)) {
            return deepMap(src, targetField.getAnnotation(MapComponentType.class), targetClass);
        } else {
            return deepMap(src, targetClass);
        }
    }

    private static Object deepMap(Map src, MapComponentType mapAnno, Class targetClass) {
        Map valueMap;
        if (Modifier.isAbstract(targetClass.getModifiers()) || Modifier.isInterface(targetClass.getModifiers())) {
            if (targetClass.isAssignableFrom(HashMap.class)) {
                valueMap = Maps.newHashMap();
            } else {
                return null;
            }
        } else {
            valueMap = (Map) newInstance(targetClass);
        }
        if (mapAnno != null) {
            Class keyClass = mapAnno.key();
            Class valueClass = mapAnno.value();
            for (Object key : src.keySet()) {
                Object mappedKey = deepMap(key, keyClass);
                Object mappedValue = deepMap(src.get(key), valueClass);
                valueMap.put(mappedKey, mappedValue);
            }
        }
        return valueMap;
    }

    /**
     * 将source对象沉拷贝成满足targetField字段类型的对象.
     * 不可在convertTo中使用deepMap(this, targetClass), 会引起循环引用问题.
     *
     * @param source      源
     * @param targetField 目标所在的对象中的字段名称
     * @param target      目标所在的对象
     * @return 目标所在的对象中的字段对象
     */
    public static Object deepMap(Object source, String targetField, Object target) {
        if (source == null || targetField == null || target == null) {
            return null;
        }
        Field field = ReflectionUtil.getAccessibleField(target, targetField);
        return deepMap(source, field, target);
    }


    /**
     * 将source对象沉拷贝成满足targetField字段类型的对象.
     * 不可在convertTo中使用deepMap(this, targetClass), 会引起循环引用问题.
     *
     * @param source      源
     * @param targetField 目标所在的对象中的字段类型
     * @param target      目标所在的对象
     * @return 目标所在的对象中的字段对象
     */
    public static Object deepMap(Object source, Field targetField, Object target) {
        if (source == null || targetField == null) {
            return null;
        }

        if (targetField.getType().isAssignableFrom(source.getClass())) {
            return source;
        }

        DateTimeType dateTimeType = targetField.getAnnotation(DateTimeType.class);
        if (dateTimeType != null && targetField.getType().isAssignableFrom(String.class)) {
            if (!(source instanceof Date)) {
                source = ConvertUtils.convert(source, Date.class);
            }
            return DateUtil.format((Date) source, dateTimeType.value());
        }

        MapperField mapperField = targetField.getAnnotation(MapperField.class);
        if (mapperField != null && !mapperField.convertFrom()) {
            return null;
        }
        Class targetClass = targetField.getType();
        //适配id等接口类型的数据，根据idClass的类型实例化字段
        if (isAbstractClassDefinedInTargetClass(target, targetClass)) {
            try {
                targetClass = (Class) ReflectionUtil.getFieldValue(target, targetField.getName() + "Class");
            } catch (IllegalArgumentException e) {
                // ignore
            }
        }
        if (targetField.isEnumConstant() || source.getClass().isEnum()) {
            return deepMapEnum(source, targetClass);
        }
        if (source instanceof Collection && Collection.class.isAssignableFrom(targetClass)) {
            return deepMap((Collection) source, targetField);
        }
        if (source instanceof Map) {
            return deepMap((Map) source, targetField);
        }
        Object result = null;
        if (source instanceof ConvertTo) {
            try {
                result = ((ConvertTo) source).convertTo();
            } catch (Exception e) {
                // ignore
            }
            if (isMatchObject(targetClass, result)) {
                return result;
            }
        }
        if (ConvertFrom.class.isAssignableFrom(targetClass)) {
            try {
                result = newInstance(targetClass);
                ((ConvertFrom) result).convertFrom(source);
                return result;
            } catch (Exception e) {
                // ignore
            }
        }
        try {
            result = ConvertUtils.convert(source, targetClass);
        } catch (Exception e) {
            // ignore
        }
        if (isMatchObject(targetClass, result)) {
            return result;
        }
        return deepMap(source, targetClass);
    }

    private static boolean isAbstractClassDefinedInTargetClass(Object target, Class targetClass) {
        return !isPrimaryOrWrapClass(targetClass)
                && (Modifier.isInterface(targetClass.getModifiers()) || Modifier.isAbstract(targetClass.getModifiers()))
                && target != null;
    }

    /**
     * 身拷贝源对象到目标对象.
     *
     * @param source       源对象实体
     * @param targetObject 目标对象实体
     */
    public static void deepCopy(final Object source, final Object targetObject) throws ConversionException {
        if (source == null || targetObject == null) {
            return;
        }
        final Class targetClass = targetObject.getClass();
        Class srcClass = source.getClass();
        if (Map.class.isAssignableFrom(srcClass)) {
            for (String fieldName : ((Map<String, Object>) source).keySet()) {
                Object srcValue = ((Map<String, Object>) source).get(fieldName);
                Object value = deepMap(srcValue, ReflectionUtil.getAccessibleField(targetClass, fieldName), targetObject);
                if (value != null) {
                    ReflectionUtil.setFieldValue(targetObject, fieldName, value);
                }
            }
        } else {
            org.shj.spring.boot.lang.BeanCopier copier = org.shj.spring.boot.lang.BeanCopier.getBeanCopier(srcClass, targetClass);
            copier.copy(source, targetObject, (src, srcField, targetField) -> deepMap(src, targetField, targetObject));

        }
    }

    private static boolean isMatchObject(Class targetClass, Object result) {
        return result != null && targetClass.isAssignableFrom(result.getClass());
    }

    /**
     * 将满足实现ConvertTo接口的Collection转换成目标Collection.
     *
     * @param sourceList 源Collection
     * @param <E>        转换源类型
     * @param <T>        转换目标类型
     * @return 包含转换目标的Collection
     */
    public static <E extends ConvertTo<T>, T> List<T> convertTo(Collection<E> sourceList) {
        return convert(sourceList, E::convertTo);
    }

    /**
     * 将满足实现ConvertTo接口的Collection转换成目标Collection.
     *
     * @param sourceList 源Collection
     * @param <E>        转换源类型
     * @param <T>        转换目标类型
     * @return 包含转换目标的Collection
     */
    public static <E, T> List<T> convert(Collection<E> sourceList, Function<E, T> converter) {
        List<T> destinationList = Lists.newArrayList();
        if (CollectionUtils.isNotEmpty(sourceList)) {
            for (E sourceObject : sourceList) {
                T destinationObject = converter.apply(sourceObject);
                destinationList.add(destinationObject);
            }
        }
        return destinationList;
    }

    /**
     * 获取当前类到目标类的拷贝对象.
     *
     * @param srcClass     当前类
     * @param targetClass  目标类
     * @param useConverter 是否使用自定义转换
     * @return BeanCopier
     */
    private static BeanCopier getBeanCopier(Class srcClass, Class targetClass, boolean useConverter) {
        String beanKey = generateKey(srcClass, targetClass);
        BeanCopier copier = BEAN_COPIER_MAP.get(beanKey);
        if (copier == null) {
            copier = BeanCopier.create(srcClass, targetClass, useConverter);
            BEAN_COPIER_MAP.put(beanKey, copier);
        }
        return copier;
    }

    /**
     * 将非空值从source搬运到destination.
     *
     * @param source 源
     * @param target 目标对象
     */
    public static void patch(Object source, Object target) {
        patch(source, target, null, null);
    }

    /**
     * 将非空值从source搬运到destination.
     *
     * @param source 源
     * @param target 目标对象
     */
    public static void patch(Object source, Object target, Set<String> includeColumns, Set<String> excludeColumns) {
        if (source == null || target == null) {
            return;
        }
        boolean isAll = CollectionUtils.isEmpty(includeColumns);
        if (excludeColumns == null) {
            excludeColumns = Sets.newHashSet();
        }
        MethodAccess methodAccess = ReflectionUtil.getMethodAccess(source.getClass());
        for (int index = 0; index < methodAccess.getMethodNames().length; index++) {
            String methodName = methodAccess.getMethodNames()[index];
            Class[] classes = methodAccess.getParameterTypes()[index];
            if (StringUtils.startsWith(methodName, "get") && classes.length == 0) {
                String fieldName = StringUtils.uncapitalize(StringUtils.removeStart(methodName, "get"));
                if (excludeColumns.contains(fieldName) || (!isAll && !includeColumns.contains(fieldName))) {
                    continue;
                }
                Object value = methodAccess.invoke(source, methodName);
                if (value != null) {
                    ReflectionUtil.setFieldValue(target, StringUtils.uncapitalize(StringUtils.removeStart(methodName, "get")), value);
                }
            }
        }
    }

}